package net.aiion.weave.impl.bytecode;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.bytecode.BytecodeContext;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.bytecode.BytecodeSink;
import net.aiion.weave.spi.bytecode.BytecodeSource;
import net.aiion.weave.spi.bytecode.ResumptionCondition;
import net.aiion.weave.spi.bytecode.events.BytecodeEvent;

public class BytecodeContextImpl implements BytecodeContext {
    private final BytecodeSource bytecodeSource;
    private final Map<String, Object> properties;

    private List<BytecodeSink> bytecodeSinks;

    private int currentBytecodeSinkIndex;
    private BytecodeEvent currentBytecodeEvent;

    private BytecodeGenerator bytecodeGenerator;

    public BytecodeContextImpl(final BytecodeSource aBytecodeSource) {
        bytecodeSource = aBytecodeSource;
        properties = new HashMap<>();
    }

    @Override
    public void postponeSourcing() {
        bytecodeSource.postponeSourcing();
    }

    @Override
    public void postponeSourcing(final ResumptionCondition aResumptionCondition) {
        bytecodeSource.postponeSourcing(aResumptionCondition);
    }

    @Override
    public void cancelSourcing() {
        bytecodeSource.cancelSourcing();
    }

    @Override
    public boolean isPostponed() {
        return bytecodeSource.isPostponed();
    }

    @Override
    public boolean isCancelled() {
        return bytecodeSource.isCancelled();
    }

    public void setBytecodeSinks(final List<BytecodeSink> aBytecodeSinks) {
        bytecodeSinks = aBytecodeSinks;
    }

    @Override
    public void setProperty(final String aName, final Object aValue) {
        properties.put(aName, aValue);
    }

    @Override
    public void removeProperty(final String aName) {
        properties.remove(aName);
    }

    @Override
    public Object getProperty(final String aName) {
        return properties.get(aName);
    }

    @Override
    public <T> T getProperty(final String aName, final Class<T> aType) {
        final Object o = getProperty(aName);
        if (aType.isInstance(o)) {
            return aType.cast(o);
        }

        return null;
    }

    @Override
    public BytecodeEvent getBytecodeEvent() {
        return currentBytecodeEvent;
    }

    @Override
    public BytecodeGenerator getBytecodeGenerator() {
        if (bytecodeGenerator == null) {
            bytecodeGenerator = Bytecode.createGenerator(new DispatchingBytecodeSink());
        }

        return bytecodeGenerator;
    }

    @Override
    public void forwardBytecodeEvent(final BytecodeEvent aBytecodeEvent) {
        if (isPostponed() || isCancelled()) {
            return;
        }

        if (currentBytecodeSinkIndex < bytecodeSinks.size()) {
            final BytecodeEvent oldBytecodeEvent = currentBytecodeEvent;
            currentBytecodeEvent = aBytecodeEvent;

            bytecodeSinks.get(currentBytecodeSinkIndex++).handleBytecodeEvent(this);

            currentBytecodeEvent = oldBytecodeEvent;
            currentBytecodeSinkIndex--;
        }
    }

    @Override
    public void forwardBytecodeEvents(final Collection<BytecodeEvent> aBytecodeEvents) {
        for (BytecodeEvent bytecodeEvent : aBytecodeEvents) {
            forwardBytecodeEvent(bytecodeEvent);
        }
    }

    private final class DispatchingBytecodeSink implements BytecodeSink {
        public DispatchingBytecodeSink() {
        }

        @Override
        public void handleBytecodeEvent(final BytecodeContext aBytecodeContext) {
            forwardBytecodeEvent(aBytecodeContext.getBytecodeEvent());
        }
    }
}
